<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>音频录制与播放</title>
</head>
<body>
<button id="startButton">开始录音</button>
<button id="stopButton" disabled>停止录音</button>
<button id="playButton" disabled>播放录音</button>

<script>
    let mediaRecorder;
    let audioChunks = [];
    let audioUrl;
    let audioBlob;
    let audioBuffer;

    // 获取麦克风权限并开始录音
    async function startRecording() {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
        mediaRecorder = new MediaRecorder(stream);
        // 录制数据
        mediaRecorder.ondataavailable = (event) => {
            audioChunks.push(event.data);
        };

        // 录音结束时处理音频数据
        mediaRecorder.onstop = async () => {
            // 录音结束，合并数据
            audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
            audioChunks = [];

            // 将音频转换为 WAV 格式二进制流
            const wavBinary = await convertToWav(audioBlob);
            audioBuffer = await new AudioContext().decodeAudioData(wavBinary);

            // 创建一个 URL 来播放 WAV 音频
            audioUrl = URL.createObjectURL(new Blob([wavBinary], { type: 'audio/wav' }));
            document.getElementById('playButton').disabled = false;
        };
        mediaRecorder.start();
        document.getElementById('stopButton').disabled = false;
    }

    // 停止录音
    function stopRecording() {
        mediaRecorder.stop();
        document.getElementById('stopButton').disabled = true;
    }

    // 播放录音
    function playRecording() {
        const audioElement = new Audio(audioUrl);
        audioElement.play();
    }

    // 将音频数据转换为 WAV 格式的二进制流
    async function convertToWav(audioBlob) {
        const arrayBuffer = await audioBlob.arrayBuffer();
        const audioBuffer = await new AudioContext().decodeAudioData(arrayBuffer);
        return encodeWAV(audioBuffer);
    }

    // 将 AudioBuffer 转换为 WAV 格式
    function encodeWAV(audioBuffer) {
        const numOfChannels = audioBuffer.numberOfChannels;
        const sampleRate = audioBuffer.sampleRate;
        const length = audioBuffer.length * numOfChannels;
        const buffer = new ArrayBuffer(44 + length * 2); // 44 字节 WAV 头
        const view = new DataView(buffer);

        // 写入 WAV 头
        writeString(view, 0, 'RIFF');
        view.setUint32(4, 36 + length * 2, true);
        writeString(view, 8, 'WAVE');
        writeString(view, 12, 'fmt ');
        view.setUint32(16, 16, true); // fmt 长度
        view.setUint16(20, 1, true);  // PCM 格式
        view.setUint16(22, numOfChannels, true);
        view.setUint32(24, sampleRate, true);
        view.setUint32(28, sampleRate * numOfChannels * 2, true);
        view.setUint16(32, numOfChannels * 2, true);
        view.setUint16(34, 16, true); // 16 位采样
        writeString(view, 36, 'data');
        view.setUint32(40, length * 2, true);
        // 写入音频数据
        let offset = 44;
        for (let channel = 0; channel < numOfChannels; channel++) {
            const channelData = audioBuffer.getChannelData(channel);
            for (let i = 0; i < channelData.length; i++) {
                view.setInt16(offset, channelData[i] * 0x7FFF, true); // 16-bit PCM
                offset += 2;
            }
        }

        return buffer;

        // 辅助函数：写入字符串
        function writeString(view, offset, string) {
            for (let i = 0; i < string.length; i++) {
                view.setUint8(offset + i, string.charCodeAt(i));
            }
        }
    }

    // 按钮事件绑定
    document.getElementById('startButton').addEventListener('click', startRecording);
    document.getElementById('stopButton').addEventListener('click', stopRecording);
    document.getElementById('playButton').addEventListener('click', playRecording);
</script>
</body>
</html>
